/**
 * @file serial_port.h
 * @author 杨泽霖 (scut.bigeyoung@qq.com)
 *         赵曦 (535394140@qq.com)
 * @brief
 * @version 1.0
 * @date 2022-09-27
 *
 * @copyright Copyright SCUT RobotLab(c) 2022
 *
 */

#pragma once

#include <memory>
#include <termios.h>
#include <vector>

//! @addtogroup serial_port
//! @{

/**
 * @brief Serial communication library
 */
class SerialPort
{
    int __fd;               //!< 文件描述符
    termios __option;       //!< 终端控制
    bool __is_open = false; //!< 串口打开标志位
    int __baud_rate;        //!< 波特率

public:
    /**
     * @brief Construct a new SerialPort object
     *
     * @param baud_rate Baud rate, default B115200
     */
    explicit SerialPort(int baud_rate = B115200) : __baud_rate(baud_rate) { open(); }

    /**
     * @brief Destroy the SerialPort object
     */
    ~SerialPort() { close(); }

    /**
     * @brief Open the serial port, search all available devices automatically and try to open the first
     */
    void open();

    /**
     * @brief Close the serial port
     */
    void close();

    /**
     * @brief 从串口读取结构体
     *
     * @tparam T 读取到结构体的类型
     * @param head 头帧
     * @param tail 尾帧
     * @return std::vector<T> 读到的所有结构体
     */
    template <typename T>
    std::vector<T> readStruct(unsigned char head, unsigned char tail)
    {
        std::vector<T> vec_t;
        constexpr int LENGTH = 512;
        constexpr int SIZE = sizeof(T);
        unsigned char read_buffer[LENGTH] = {0};
        ssize_t len_result = read(read_buffer, LENGTH);
        for (ssize_t i = 0; (i + SIZE + 1) < len_result; i++)
            if (read_buffer[i] == head && read_buffer[i + SIZE + 1] == tail)
                vec_t.push_back(*(reinterpret_cast<T *>(&read_buffer[i + 1])));
        return vec_t;
    }

    /**
     * @brief Struct data write to serial port
     *
     * @tparam _Tp 写入结构体的类型
     * @param data_struct 要写入的结构体
     * @return 是否写入成功
     */
    template <typename _Tp>
    bool writeStruct(_Tp &data_struct)
    {
        ssize_t len_result = write(&data_struct, sizeof(data_struct));
        return (sizeof(data_struct) == len_result);
    }

    /**
     * @brief The serial is opened?
     *
     * @return true
     * @return false
     */
    inline bool isOpened() const { return __is_open; };

private:
    /**
     * @brief Write data
     *
     * @param data Start position of the data
     * @param length The length of the data to be written
     * @return Whether to write in full
     */
    ssize_t write(void *data, size_t len);

    /**
     * @brief Read data
     *
     * @param data Start position of the data
     * @param len The length of the data to be read
     * @return Length
     */
    ssize_t read(void *data, size_t len);
};

using serial_ptr = std::unique_ptr<SerialPort>;

//! @} serial_port
